package com.lhkj.ct.base.utils;

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.*;

/**
 * <p>
 *     数字类工具
 * </p>
 */

public class MathUtil {

    /**
     * 四舍五入保留两位小数
     */
    public static double keepTwoPrecision(Object num) {
        return getRoundingNum(num, 2);
    }

    /**
     * 数值转百分比   0.5->50.00%
     */
    public static String num2Percent(Double num, int scale) {
        NumberFormat percent = NumberFormat.getPercentInstance();
        percent.setMaximumFractionDigits(scale);
        return percent.format(num);
    }

    /**
     * 保留小数点位数，四舍五入
     * 比如处理价格等数据
     *
     * @param num 要处理的数值
     * @param n   要保留的位数
     * @return double
     */
    public static Double getRoundingNum(Object num, int n) {
        if (!(num instanceof Number)) return null;
        BigDecimal b = new BigDecimal(num.toString());
        b = b.divide(BigDecimal.ONE, n, BigDecimal.ROUND_HALF_UP);
        return b.doubleValue();
    }

    /**
     * double保留小数点位数，往上取整
     * 比如处理【评分】等数据
     *
     * @param num 要处理的double值
     * @param n   要保留的位数
     * @return
     */
    public static Double getCeilNum(Object num, int n) {
        if (!(num instanceof Number)) return null;
        BigDecimal b = new BigDecimal(String.valueOf(num));
        b = b.divide(BigDecimal.ONE, n, BigDecimal.ROUND_CEILING);
        return b.doubleValue();
    }

    /**
     * double保留小数点位数，往下取整
     * 比如处理【价格】等数据
     *
     * @param num 要处理的double值
     * @param n   要保留的位数
     * @return
     */
    public static Double getFloorNum(Object num, int n) {
        BigDecimal b = new BigDecimal(String.valueOf(num));
        b = b.divide(BigDecimal.ONE, n, BigDecimal.ROUND_FLOOR);
        return b.doubleValue();
    }

    /**
     *  两数相除保留 n 位小数
     *
     * 0：ROUND_UP 进位制：不管保留数字后面是大是小(0除外)都会进1
     * 1：ROUND_DOWN 保留设置数字，后面所有直接去除
     * 2：ROUND_CEILING 朝正无穷方向round 如果为正数，行为和round_up一样，如果为负数，行为和round_down一样
     * 3：ROUND_FLOOR 朝负无穷方向round 如果为正数，行为和round_down一样，如果为负数，行为和round_up一样
     * 4：ROUND_HALF_UP 四舍五入
     * 5：ROUND_HALF_DOWN 遇到.5的情况时往下近似,例: 1.5 ->;1 注：1.51->2
     * 6：ROUND_HALF_EVEN 如果舍弃部分左边的数字为奇数，则作   ROUND_HALF_UP   ；如果它为偶数，则作   ROUND_HALF_DOWN 又称银行家算法
     * 7：ROUND_UNNECESSARY 计算结果必须是精确的，不需要舍入的，否则抛出 ArithmeticException，既如果进度和给定数值不一致则抛异常
     *
     * @param dividend
     *                  被除数  dividend/divisor
     * @param divisor
     *                  除数
     * @param scale
     *                  要保留的位数
     */
    public static BigDecimal divide(Number dividend, Number divisor, int scale) {
        if (divisor.doubleValue() == 0){
            return BigDecimal.ZERO.setScale(scale, BigDecimal.ROUND_HALF_UP);
        }
        BigDecimal b = new BigDecimal(String.valueOf(dividend));
        return b.divide(new BigDecimal(String.valueOf(divisor)), scale, BigDecimal.ROUND_FLOOR);
    }

    /**
     *  两数相除保留 n 位小数
     *
     * @param dividend
     *                  被除数  dividend/divisor
     * @param divisor
     *                  除数
     * @param n
     *                  要保留的位数
     */
    public static String divideConvertPercent(Number dividend, Number divisor, int n) {
        NumberFormat percent = NumberFormat.getPercentInstance();
        percent.setMaximumFractionDigits(2);
        BigDecimal r = divide(dividend, divisor, n);
        return percent.format(r.doubleValue());
    }

    /**
     * 标准差σ=sqrt(s^2)
     * @param x
     * @return
     */
    public static double getStandardDeviation(List<Double> x) {
        int m = x.size();
        double sum = 0;
        for (double v : x) {// 求和
            sum += v;
        }
        double dAve = sum / m;// 求平均值
        double dVar = 0;
        for (double v : x) {// 求方差
            dVar += (v - dAve) * (v - dAve);
        }
        return getFloorNum(Math.sqrt(dVar / m), 2);
    }

    /**
     * 方差s^2=[(x1-x)^2 +...(xn-x)^2]/n
     * @param x
     * @return
     */
    public static double getVariance(double[] x) {
        int m = x.length;
        double sum = 0;
        for (int i = 0; i < m; i++) {// 求和
            sum += x[i];
        }
        double dAve = sum / m;// 求平均值
        double dVar = 0;
        for (int i = 0; i < m; i++) {// 求方差
            dVar += (x[i] - dAve) * (x[i] - dAve);
        }
        return dVar / m;
    }

    /**
     * 中位数
     */
    public static double getMedian(List<Double> x){
        int size = x.size();
        if (size % 2 == 0) {
            return  getFloorNum(x.get(size / 2 - 1) + x.get(size / 2) / 2,2);
        } else {
            return x.get(size / 2);
        }
    }

    /**
     * 众数(float)
     * 众数:在一个数组中出现次数最多的数
     * 如果存在多个众数，则一起返回
     * @param arr
     * @return
     */
    public static List<Float> getModalNums(float[] arr) {
        int n = arr.length;

        if (n == 0) {
            return new ArrayList<Float>();
        }

        if (n == 1) {
            return Arrays.asList(arr[0]);
        }

        Map<Float, Integer> freqMap = new HashMap<>();
        for (int i = 0; i < n; i++) { // 统计数组中每个数出现的频率
            Integer v = freqMap.get(arr[i]);
            // v == null 说明 freqMap 中还没有这个 arr[i] 这个键
            freqMap.put(arr[i], v == null ? 1 : v + 1);
        }

        // 将 freqMap 中所有的键值对（键为数，值为数出现的频率）放入一个 ArrayList
        List<Map.Entry<Float, Integer>> entries = new ArrayList<>(freqMap.entrySet());
        // 对 entries 按出现频率从大到小排序
        Collections.sort(entries, new Comparator<Map.Entry<Float, Integer>>() {
            @Override
            public int compare(Map.Entry<Float, Integer> e1, Map.Entry<Float, Integer> e2) {
                return e2.getValue() - e1.getValue();
            }
        });

        List<Float> modalNums = new ArrayList<>();
        modalNums.add(entries.get(0).getKey()); // 排序后第一个 entry 的键肯定是一个众数

        int size = entries.size();
        for (int i = 1; i < size; i++) {
            // 如果之后的 entry 与第一个 entry 的 value 相等，那么这个 entry 的键也是众数
            if (entries.get(i).getValue().equals(entries.get(0).getValue())) {
                modalNums.add(entries.get(i).getKey());
            } else {
                break;
            }
        }

        return modalNums;
    }

}
